package milkyway.hightech.banking;

import milkyway.hightech.entry.Entry;
import milkyway.hightech.exceptions.AmountLessThanZeroException;
import milkyway.hightech.money.Convert;
import milkyway.hightech.money.Currency;
import milkyway.hightech.money.Money;
import milkyway.hightech.timer.TimeEmitter;

import java.math.BigDecimal;
import java.math.BigInteger;

import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Stack;
import java.util.logging.Logger;

/**
 *
 * @author michael
 */
public abstract class Account extends Observable implements Observer
{
    private static final Logger logger =
        Logger.getLogger(Account.class.getName());;

    /**
     *
     */
    private List<Entry> list = new Stack<Entry>();

    /**
     * the surname and lastname of the account holder
     */
    private String accountHolder;

    // variables

    /**
     * The account number to identify the account.
     */
    private final int accountNumber;

    /**
     *
     */
    protected Calendar calendar;

    /**
     * the current account balance.
     */
    protected Money money;

    // end of variables

    /**
     *
     * @param accountNumber
     * @param accountHolder
     * @param currency
     * @param calendar
     */
    public Account(int accountNumber, String accountHolder, Currency currency,
                   Calendar calendar)
    {
        this.accountNumber = accountNumber;
        this.accountHolder = accountHolder;
        this.money = new Money(new BigInteger("0"), currency);
        this.calendar = calendar;

        TimeEmitter.getInstance().addObserver(this);
    }

    // interfaces

    /**
     * enters the given amount on credit.
     * @param amount the amount of money which should be entered on credit
     * @param reasonOfTransfer the description of this transaction
     */
    protected void enterOnCredit(Money amount, String reasonOfTransfer)
    {
        switch (amount.getValue().signum())
        {
        case -1:
        case 0:
            throw new AmountLessThanZeroException();

        case 1:
            update(1, amount, reasonOfTransfer);
        }
    }

    /**
     * enters the given amount on debit.
     * @param amount the amount of money which should be entered on debid
     * @param reasonOfTransfer the description of this transaction
     */
    protected void enterOnDebit(Money amount, String reasonOfTransfer)
    {
        switch (amount.getValue().signum())
        {
            case -1:
            case 0:
                throw new AmountLessThanZeroException();

            case 1:
                update(-1, amount, reasonOfTransfer);
            break;
        }
    }

    /**
     *updates the account balance.
     * @param typeEnter
     * @param amount
     * @param reasonOfTransfer
     */
    protected void update(int typeEnter, Money amount, String reasonOfTransfer)
    {
        char sign = '+';

        if (amount.getCurrency() != money.getCurrency())
        {
            amount = Convert.convert(amount, money.getCurrency());
        }

        if (amount.getValue().compareTo(new BigInteger("100000000")) >= 0)
        {
            String s = accountNumber + ": high amount was transfered\n";

            logger.info(s);
        }

        switch (typeEnter)
        {
        case -1:
            money = money.subtract(amount);
            sign = '-';

            break;

        case 1:
            money = money.add(amount);

            break;

        default:
            throw new TypeNotPresentException(Integer.toString(typeEnter),
                                              null);
        }

        list.add(new Entry(reasonOfTransfer, sign, amount));
    }

    /**
     * pays interests to the account balance.
     */
    protected abstract void payInterest();

    /**
     * returns the account number.
     * @return the account number
     */
    public int getAccountNumber()
    {
        return accountNumber;
    }

    /**
     * returns the name of the account holder.
     * @return the account holder
     */
    public String getAccountHolder()
    {
        return accountHolder;
    }

    /**
     * returns the current account balance.
     * @return the current account balance
     */
    public BigInteger getAccountBalance()
    {
        return money.getValue();
    }

    /**
     *
     * @return
     */
    public Currency getCurrency()
    {
        return money.getCurrency();
    }

    /**
     *
     * @return the head of the account statement
     */
    @Override
    public String toString()
    {
        BigDecimal d =
            new BigDecimal(money.getValue().toString()).divide(BigDecimal.valueOf(100));
        String data = "Account holder: "
                      + accountHolder
                      + "\nAccount number: "
                      + accountNumber
                      + "\nAccount balance: "
                      + d + " "
                      + money.getCurrency();

        return data;
    }

    /**
     *
     * @param c
     */
    public void sortList(Comparator<Entry> c)
    {
        Collections.sort(list, c);
    }

    /**
     *
     * @return
     */
    public Iterator<Entry> getIterator()
    {
        return list.iterator();
    }
}
